/*
 * bw_FcgiRequest.cpp
 *
 *  Created on: 2018年9月3日
 *  Author: Degang.Wu (windragon@126.com)
 *  Desc:
 */

#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "bw_FcgiRequest.h"
#include "fcgio.h"
#include "fcgi_stdio.h"
#include "bw_Str.h"
#include "cJSON.h"
#include "mod/bw_JsonParse.h"

using namespace std;

#define _MAX_LOG_SIZE 2048
#define BWFCGI_LOG_FILE		"/var/log/bwfcgi.log"

static const unsigned long STDIN_MAX = 1024*1024*10;

#define		FCGI_ROLE      "FCGI_ROLE"
#define		CONTENT_LENGTH      "CONTENT_LENGTH"
#define		QUERY_STRING      "QUERY_STRING"
#define		REQUEST_URI      "REQUEST_URI"
#define 	CONTENT_TYPE	 "CONTENT_TYPE"
#define		REDIRECT_STATUS      "REDIRECT_STATUS"
#define		SCRIPT_NAME      "SCRIPT_NAME"
#define		SCRIPT_FILENAME      "SCRIPT_FILENAME"
#define		DOCUMENT_ROOT      "DOCUMENT_ROOT"
#define		REQUEST_METHOD      "REQUEST_METHOD"
#define		SERVER_PROTOCOL      "SERVER_PROTOCOL"
#define		SERVER_SOFTWARE      "SERVER_SOFTWARE"
#define		GATEWAY_INTERFACE      "GATEWAY_INTERFACE"
#define		REQUEST_SCHEME      "REQUEST_SCHEME"
#define		SERVER_PORT      "SERVER_PORT"
#define		SERVER_ADDR      "SERVER_ADDR"
#define		SERVER_NAME      "SERVER_NAME"
#define		REMOTE_ADDR      "REMOTE_ADDR"
#define		REMOTE_PORT      "REMOTE_PORT"
#define		HTTP_HOST      "HTTP_HOST"
#define		HTTP_CONNECTION      "HTTP_CONNECTION"
#define		HTTP_CONTENT_LENGTH      "HTTP_CONTENT_LENGTH"
#define		HTTP_USER_AGENT      "HTTP_USER_AGENT"
#define		HTTP_CACHE_CONTROL      "HTTP_CACHE_CONTROL"
#define		HTTP_ORIGIN      "HTTP_ORIGIN"
#define		CONTENT_TYPE      "CONTENT_TYPE"
#define		HTTP_ACCEPT      "HTTP_ACCEPT"
#define		HTTP_ACCEPT_ENCODING      "HTTP_ACCEPT_ENCODING"
#define		HTTP_ACCEPT_LANGUAGE      "HTTP_ACCEPT_LANGUAGE"
#define 	PATH_INFO		"PATH_INFO"

#define 	MIME_FORM_DATA	"multipart/form-data;"
#define 	BOUNDARY		"boundary="


namespace bwcgi
{

int FcgiRequest::bDebug = 0;
bwHttpHandle FcgiRequest::handler = NULL;
std::map<std::string, bwHttpHandle> FcgiRequest::routers;

const char* vars[] = {
		FCGI_ROLE,
		CONTENT_LENGTH,
		QUERY_STRING,
		REQUEST_URI,
		REDIRECT_STATUS,
		SCRIPT_NAME,
		SCRIPT_FILENAME,
		DOCUMENT_ROOT,
		REQUEST_METHOD,
		SERVER_PROTOCOL,
		SERVER_SOFTWARE,
		GATEWAY_INTERFACE,
		REQUEST_SCHEME,
		SERVER_PORT,
		SERVER_ADDR,
		SERVER_NAME,
		REMOTE_ADDR,
		REMOTE_PORT,
		HTTP_HOST,
		HTTP_CONNECTION,
		HTTP_CONTENT_LENGTH,
		HTTP_USER_AGENT,
		HTTP_CACHE_CONTROL,
		HTTP_ORIGIN,
		CONTENT_TYPE,
		HTTP_ACCEPT,
		HTTP_ACCEPT_ENCODING,
		HTTP_ACCEPT_LANGUAGE,
		PATH_INFO
};

FcgiRequest::FcgiRequest(const FCGX_Request& _request)
	: request(_request)
	, pBuffer(NULL)
	, bEnableDown(false)
	, pDownBuf(NULL)
{
	// TODO Auto-generated constructor stub
	parseReq();
	cgi_debug("new request, [%s:%d] %s", getRemoteAddr(), getRemotePort(), getURI());
	if (strncmp("POST", getMethod(), strlen("POST")) == 0)
	{
		readBody(_request);
	}

}

FcgiRequest::~FcgiRequest()
{
	// TODO Auto-generated destructor stub
	SAFE_FREE_PTR(pBuffer);
	SAFE_FREE_PTR(pDownBuf);
	if (!sUploadfile.empty() && FCGI_file_exist(sUploadfile.c_str()))
	{
		// delete upload tmp file
		remove(sUploadfile.c_str());
	}
}

const char* FcgiRequest::defaultHandler(FcgiRequest* pFcgiReq)
{
	std::string sMethod = pFcgiReq->getMethod();
	if (sMethod == "GET")
	{
		return pFcgiReq->getURI();
	}
	else if (sMethod == "POST")
	{
		return pFcgiReq->getBody();
	}
	else
	{
		return "Unknow request";
	}
}

void FcgiRequest::handleRequest()
{
	const char* sResp = NULL;

	std::string baseUri(getBaseUri());
	if (getPathinfo())
	{
		baseUri += getPathinfo();
	}
	cgi_debug("uri=%s", baseUri.c_str());

	if (!baseUri.empty()) {
		if (FcgiRequest::routers.find(baseUri) != FcgiRequest::routers.end())
		{
			sResp = FcgiRequest::routers[baseUri](this);
		} else {
			sResp = BWFCGI_WRONG_RESP;
		}
	}
	else if (FcgiRequest::handler)
	{
		sResp = FcgiRequest::handler(this);
	}
	else if (baseUri == "/")
	{
		sResp = FcgiRequest::defaultHandler(this);
	}
	else
	{
		sResp = BWFCGI_WRONG_RESP;
	}
	if(!sResp || strlen(sResp) == 0)
	{
		sResp = BWFCGI_WRONG_RESP;
	}

	response(sResp);
}

void FcgiRequest::response(const char* sResp, const char* mime_type)
{
    fcgi_streambuf cout_fcgi_streambuf(request.out);
    cout.rdbuf(&cout_fcgi_streambuf);

    if (bEnableDown && !sDownloadfile.empty())
    {
    	// application/octet-stream
    	cgi_debug("download file[%s], len=[%d]", sDownloadfile.c_str(), FCGI_file_size(sDownloadfile.c_str()));
    	PATH path;
    	FCGI_file_split(&path, sDownloadfile.c_str());

    	U32 filesize = FCGI_file_size(sDownloadfile.c_str());
    	pDownBuf = (U8*)malloc(filesize + 1);
    	FCGI_file_read(sDownloadfile.c_str(), pDownBuf, filesize);

    	if (sDownRename.empty())
    	{
    		cout << "Content-Disposition: attachment; filename=" << path.file << "\r\n";
    	}
    	else
    	{
    		cout << "Content-Disposition: attachment; filename=" << sDownRename << "\r\n";
    	}
    	cout << "Content-type: application/octet-stream" << "\r\n\r\n";
    	cout.write((char*)pDownBuf, filesize);

    	SAFE_FREE_PTR(pDownBuf);
    }
    else
    {
    	// text/plain
		std::string mime = mime_type;
		JsonParse jp(sResp);
		if (jp.jsonValid())
		{
			mime = "application/json" ;
		}
		cgi_debug("len=[%d],mime=%s\n%s", strlen(sResp), mime.c_str(), sResp);
		cout << "Content-type: " << mime << "\r\n\r\n"
				<< sResp;
    }

}

void FcgiRequest::readBody(const FCGX_Request& _request)
{
	std::string smime = getMimetype();
	cgi_debug("mime:%s", smime.c_str());
	if (smime.find(MIME_FORM_DATA) != std::string::npos)
	{
		readBody_bin(_request);
	}
	else
	{
		readBody_plain(_request);
	}

}

void FcgiRequest::readBody_bin(const FCGX_Request& _request)
{
	fcgi_streambuf cin_fcgi_streambuf(_request.in);
	fcgi_streambuf cout_fcgi_streambuf(_request.out);
	fcgi_streambuf cerr_fcgi_streambuf(_request.err);

	cin.rdbuf(&cin_fcgi_streambuf);
	cout.rdbuf(&cout_fcgi_streambuf);
	cerr.rdbuf(&cerr_fcgi_streambuf);

	std::string sboundary = getBoundary();
	cgi_debug("boundary:%s", sboundary.c_str());

	const char * content_length_str = getParam(CONTENT_LENGTH); //FCGX_GetParam("CONTENT_LENGTH", _request.envp);
	unsigned long content_length = STDIN_MAX;
	FILE *fp = NULL;
	int getState = STATE_START;
	int contentLength = -1; /* uploaded file content length */
	int nowReadLen = 0;
	int signCodeLen = 0;
	int tmpLen = 0;
	char *nowReadP = NULL;
	char *nowWriteP = NULL;
	char dealBuf[DEAL_BUF_LEN];
	char signCode[SIGN_CODE_LEN]; /* http boundary */
	char tmpSignCode[SIGN_CODE_LEN];
	char fileName[FILE_NAME_LEN];
	const char *saveas = "/tmp/";

	memset(dealBuf, 0, DEAL_BUF_LEN);
	memset(signCode, 0, SIGN_CODE_LEN);
	memset(fileName, 0, FILE_NAME_LEN);
	nowReadLen = 0;

	if (content_length_str)
	{
		content_length = atol(content_length_str);

		if (content_length > STDIN_MAX)
		{
			content_length = STDIN_MAX;
		}
	}
	else
	{
		// Do not read from stdin if CONTENT_LENGTH is missing
		content_length = 0;
	}
	cgi_debug("content length:%d", content_length);

	if (content_length <= 0)
	{
		return;
	}

	contentLength = content_length;
	while (contentLength > 0)
	{
		if (contentLength >= DEAL_BUF_LEN)
		{
			nowReadLen = DEAL_BUF_LEN;
		}
		else
		{
			nowReadLen = contentLength;
		}
		contentLength -= nowReadLen;

		cin.read(dealBuf, nowReadLen);
		nowReadP = dealBuf;
		while (nowReadLen > 0)
		{
			switch (getState)
			{
			case STATE_START:
				nowWriteP = signCode;
				getState = STATE_GET_SIGN_CODE;
			case STATE_GET_SIGN_CODE:
				if (strncmp(nowReadP, "\r\n", 2) == 0)
				{
					signCodeLen = nowWriteP - signCode;
					nowReadP++;
					nowReadLen--;
					*nowWriteP = 0;
					cgi_debug("signCode:%s", signCode);
					getState = STATE_GET_FILE_NAME;
				}
				else
				{
					*nowWriteP = *nowReadP;
					nowWriteP++;
				}
				break;
			case STATE_GET_FILE_NAME:
				if (strncmp(nowReadP, "filename=", strlen("filename=")) == 0)
				{
					nowReadP += strlen("filename=");
					nowReadLen -= strlen("filename=");
					nowWriteP = fileName + strlen(saveas);
					while (*nowReadP != '\r')
					{
						if (*nowReadP == '\\' || *nowReadP == '/')
						{
							nowWriteP = fileName + strlen(saveas);
						}
						else if (*nowReadP != '\"')
						{
							*nowWriteP = *nowReadP;
							nowWriteP++;
						}
						nowReadP++;
						nowReadLen--;
					}
					*nowWriteP = 0;
					nowReadP++;
					nowReadLen--;
					getState = STATE_GET_FILE_START;
					memcpy(fileName, saveas, strlen(saveas));
					if ((fp = fopen(fileName, "w")) == NULL)
					{
						cgi_debug("open file %s error %d", fileName, errno);
						return;
					}
					else
					{
						sUploadfile = fileName;
						cgi_debug("open file:%s ok!", fileName);
					}
				}
				break;
			case STATE_GET_FILE_START:
				if (strncmp(nowReadP, "\r\n\r\n", 4) == 0)
				{
					nowReadP += 3;
					nowReadLen -= 3;
					getState = STATE_GET_FILE_CONTENT;
				}
				break;
			case STATE_GET_FILE_CONTENT:
				if (*nowReadP != '\r')
				{
					fputc(*nowReadP, fp);
				}
				else
				{
					if (nowReadLen >= (signCodeLen + 2)) //\r\n=2
					{
						if (strncmp(nowReadP + 2, signCode, signCodeLen) == 0)
						{
							getState = STATE_END;
							nowReadLen = 1;
						}
						else
						{
							fputc(*nowReadP, fp);
						}
					}
					else
					{
						getState = STATE_CHECK_END;
						nowWriteP = tmpSignCode;
						*nowWriteP = *nowReadP;
						nowWriteP++;
						tmpLen = 1;
					}
				}
				break;
			case STATE_CHECK_END:
				if (*nowReadP != '\r')
				{
					if (tmpLen < signCodeLen + 2)
					{
						*nowWriteP = *nowReadP;
						nowWriteP++;
						tmpLen++;
						if (tmpLen == signCodeLen + 2)
						{
							*nowWriteP = 0;
							if ((tmpSignCode[1] == '\n')
									&& (strncmp(tmpSignCode + 2, signCode,
											signCodeLen) == 0))
							{
								getState = STATE_END;
								nowReadLen = 1;
							}
							else
							{
								//fprintf(fp,tmpSignCode);
								fwrite(tmpSignCode, sizeof(char), tmpLen, fp);
								getState = STATE_GET_FILE_CONTENT;
							}
						}
					}
				}
				else
				{
					*nowWriteP = 0;
					//fprintf(fp,tmpSignCode);
					fwrite(tmpSignCode, sizeof(char), tmpLen, fp);
					nowWriteP = tmpSignCode;
					*nowWriteP = *nowReadP;
					nowWriteP++;
					tmpLen = 1;
				}
				break;
			case STATE_END:
				nowReadLen = 1;
				break;
			default:
				break;
			}
			nowReadLen--;
			nowReadP++;
		}
	}
	if (fp != NULL)
	{
		fclose(fp);
	}

	if (FCGI_file_exist(sUploadfile.c_str()))
	{
		iBufferLen = FCGI_file_size(sUploadfile.c_str());
		pBuffer = (U8*) malloc(iBufferLen + 1);
		memset(pBuffer, 0, iBufferLen + 1);
		FCGI_file_read(sUploadfile.c_str(), pBuffer, iBufferLen);
	}
}

void FcgiRequest::readBody_plain(const FCGX_Request& _request)
{
	sBody.clear();
    fcgi_streambuf cin_fcgi_streambuf(_request.in);
    fcgi_streambuf cout_fcgi_streambuf(_request.out);
    fcgi_streambuf cerr_fcgi_streambuf(_request.err);

    cin.rdbuf(&cin_fcgi_streambuf);
    cout.rdbuf(&cout_fcgi_streambuf);
    cerr.rdbuf(&cerr_fcgi_streambuf);

	char * content_length_str = FCGX_GetParam("CONTENT_LENGTH", _request.envp);

	unsigned long content_length = STDIN_MAX;

	if (content_length_str)
	{
		content_length = strtol(content_length_str, &content_length_str, 10);

		if (content_length > STDIN_MAX)
		{
			content_length = STDIN_MAX;
		}
	}
	else
	{
		// Do not read from stdin if CONTENT_LENGTH is missing
		content_length = 0;
	}

	if (content_length > 0)
	{
		char * content_buffer = new char[content_length+1];
		bzero(content_buffer, content_length+1);
		cin.read(content_buffer, content_length);
		content_length = cin.gcount();

		do
		{
			cin.ignore(1024);
		} while (cin.gcount() == 1024);

		sBody = content_buffer;
		delete[] content_buffer;

	}
}

const char* FcgiRequest::getBody() const
{
	if (!sBody.empty())
	{
		return sBody.c_str();
	}
	else
	{
		return (const char*)pBuffer;
	}
}

size_t	FcgiRequest::getBodyLen()
{
	if (!sBody.empty())
	{
		return sBody.size();
	}
	else
	{
		return iBufferLen;
	}
}

void FcgiRequest::parseReq()
{
	for (char** p = request.envp; *p; p++)
	{
//		cgi_debug("%s", *p);
		std::string line(*p);
		size_t equPos = line.find("=");
		if (equPos != std::string::npos)
		{
			std::string key = line.substr(0, equPos);
			std::string val = line.substr(equPos+1);
			serverVars[key] = val;
		}
	}

	string  sQuery(getQueryString());
	if (!sQuery.empty())
	{
		BWStr::Array lines;
		BWStr::split_ex(lines, sQuery, "&");

		for (size_t i = 0; i < lines.size(); i++)
		{
			// a=1
			size_t equPos = lines[i].find("=");
			if (equPos != std::string::npos)
			{
				std::string key = lines[i].substr(0, equPos);
				std::string val = lines[i].substr(equPos+1);
				querys[key] = val;
			}
		}
	}
}

const char* FcgiRequest::getParam(const char* key)
{
	if ( key && serverVars.find(key) != serverVars.end())
	{
		return serverVars[key].c_str();
	} else {
		return NULL;
	}
}

const char* FcgiRequest::getQueryString()
{
	return getParam(QUERY_STRING);
}

const char* FcgiRequest::getQuery(const char* key)
{
	if (key && querys.find(key) != querys.end())
	{
		return querys[key].c_str();
	}
	return NULL;
}

const char* FcgiRequest::getUploadfile()
{
	return sUploadfile.c_str();
}

U32	FcgiRequest::getUploadfileLen()
{
	return iBufferLen;
}

int	FcgiRequest::setDownloadfile(const char* fname, const char* _rename)
{
	if (!FCGI_file_exist(fname))
	{
		return -1;
	}
	sDownloadfile = fname;
	if (_rename && strlen(_rename))
	{
		sDownRename = _rename;
	}
	return 0;
}

void FcgiRequest::enableDown()
{
	bEnableDown = true;
}

const char* FcgiRequest::getRemoteAddr()
{
	const char* addr = getParam(REMOTE_ADDR);
	if (addr)
	{
		return addr;
	} else {
		return getParam("REMOTE_HOST");
	}
}

const char* FcgiRequest::getBaseUri()
{
	return getParam(SCRIPT_NAME);
}

const char* FcgiRequest::getPathinfo()
{
	return getParam(PATH_INFO);
}

int FcgiRequest::getRemotePort()
{
	const char* sPort = getParam(REMOTE_PORT);
	return atoi(sPort);
}

const char* FcgiRequest::getMethod()
{
	return getParam(REQUEST_METHOD);
}

const char* FcgiRequest::getUserAgent()
{
	return getParam(HTTP_USER_AGENT);
}

const char* FcgiRequest::getURI()
{
	return getParam(REQUEST_URI);
}

const char* FcgiRequest::getMimetype()
{
	return getParam(CONTENT_TYPE);
}

const char* FcgiRequest::getBoundary()
{
	std::string smime = getMimetype();
	if (smime.find(MIME_FORM_DATA) == std::string::npos)
	{
		return NULL;
	}

	size_t pos = smime.find(BOUNDARY);
	if (pos == std::string::npos)
	{
		return NULL;
	}

	sBoundary = smime.substr(pos + strlen(BOUNDARY));
	return sBoundary.c_str();
}

void FcgiRequest::registeHandle(bwHttpHandle _handler)
{
	if (_handler)
	{
		FcgiRequest::handler = _handler;
	}
}

void FcgiRequest::registeHandle(const char* uri, bwHttpHandle _handler)
{
	if (uri && (strlen(uri) > 0) && _handler)
	{
		std::string sUri(uri);

		FcgiRequest::routers[uri] = _handler;
	}
}

bool FcgiRequest::isDebug()
{
	return !!FcgiRequest::bDebug;
}

void FcgiRequest::setDebug(int _debug)
{
	FcgiRequest::bDebug = _debug;
}

void registeHttpHandle(bwHttpHandle _handler)
{
	FcgiRequest::registeHandle(_handler);
}

void registeHttpHandle(const char* uri, bwHttpHandle _handler)
{
	FcgiRequest::registeHandle(uri, _handler);
}

void setDebug(int _debug)
{
	FcgiRequest::setDebug(_debug);
}

/*
 * debug file path: /var/log/bwfcgi.log
 */
int _cgi_debug(const char *format, ...)
{
	if (FcgiRequest::isDebug())
	{
		va_list params;
		va_start(params, format);

		char _msg[SZ_2K];
		memset(_msg, 0, sizeof(_msg));
		vsnprintf(_msg, sizeof(_msg), format, params);

		_FCGI_log(_msg);

		va_end(params);
	}

	return 0;
}



} /* namespace bwcgi */
